package com.GPS_based_training.progetto_so2_2014_2015;


import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;

import org.json.JSONException;
import org.json.JSONObject;

import com.GPS_based_training.JSONUtils.JSONFileManager;
import com.GPS_based_training.JSONUtils.JSONStandardization;
import com.GPS_based_training.database.*;
import com.GPS_based_training.progetto_so2_2014_2015.R;
import com.GPS_based_training.support.Training;


import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.ListView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.widget.SimpleAdapter;
import android.widget.AdapterView.*;
import android.widget.Toast;


/**
 * @author Danilo Dessi' & Pietro Meloni & Andreas Scalas
 * 
 * The aim of this class is shown the list of training that are saved in the device.
 * The trainings are shown in a list.
 *
 */
public class TrainingsHistoryActivity extends ActionBarActivity {

	/****** GRAPHIC OBJECTS *******/
	private ListView lvTrainings;			//The list that show the trainings			
	private DatabaseAdapter dba;			//The database of the application
	private Intent intentTrainingDetails;	//intent to show the details of one training in new activity
	private ArrayList<String> idKeys;		//A array to manage the keys of the trainings that are assigned by database
	private SimpleAdapter adapter;			//A adapter to show the training name and date in a list for each training
	private HashMap<String, String> hmapAllTrainings; //A map to save pair key-json_string for each training
	private String stringSearch;			//String that allows the search of the trainings
	
	
	/******* CONSTANTS *******/
	//Constants to retrieve in the name and date of the json object 
	private static final int FILE_SELECT_CODE = 0;
	private static final int DB_IMPORT_CODE = 1;
	private final String NAME = "name";
	private final String DATE = "date";	
	private final String DISTANCE = "distance";
	private final double MULTIPLIER_FROM_METERS_TO_KILOMETERS = 0.001;
	private final String PATH_FILE = "file:///sdcard/Android/data/com.GPS-Based-Training.GPS-Based-Training/file";
	private final String EXT = ".json";
	private final int AVAILABLE = 1;

	/******* VARIABLES TO MANAGE THREAD *******/
	private Semaphore semaphore; // A semaphore to manage critical session
	
	
	/******* LIFE-CYCLE METHODS ******/
	@Override
	/**
	 * @author Danilo Dessi'
	 * This method creates the activity
	 */
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_trainings_history);
		
		//initialization of elements of view
		intentTrainingDetails = new Intent(this, TrainingDetailsActivity.class);
		lvTrainings = (ListView) findViewById(R.id.lv_trainings);
		
		//initialization string for the search
		stringSearch = "";
		
		//semaphore to manage the update of the view
		semaphore = new Semaphore(AVAILABLE, true);
		
		//Retrieval of all trainings from database
		hmapAllTrainings = allTrainingsFromDatabase();
		
		//setting of view
		setView();
		
	}
	
	
	@Override
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method deletes the activity. It delete all files on devices that was created
	 */
	public void onDestroy(){
		super.onDestroy();
		
		JSONFileManager.deleteAllFile();
	}
	
	
	/******* CONTENT AND CONTEXT MENU METHODS *******/
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method creates the contextual menu
	 */
	public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {  
	 	 super.onCreateContextMenu(menu, v, menuInfo);  
	 	 getMenuInflater().inflate(R.menu.contextual_training_history_menu, menu);
	     menu.setHeaderTitle(R.string.Options);  
	     
	}

	/**
	 * @author Danilo Dessi'
	 * 
	 * This method associates the menu items with the actions 
	 */
	public boolean onContextItemSelected(MenuItem item) {
		
		 //Retrieval of position of the item selected; it is used to permit the elimination
		 //of the training from database 
		 int pos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
				 
		 //Switch on id of the item the is selected
         switch(item.getItemId()) {
	        case R.id.delete_training:
	        	//choice: delete training
	        	//to delete training we use the deleteTraining method
	        	if (deleteTraining(pos)) {
	        		
	        		//we show the status of operation
	        		Toast.makeText(this, getString(R.string.training_deleted), Toast.LENGTH_SHORT).show();
	        	
	        	} else {
	        		
	        		//we show the status of operation
	        		Toast.makeText(this, getString(R.string.elimination_failed), Toast.LENGTH_SHORT).show();
	        		
	        	}
	        	
	        	//Retrieval of all trainings from database
	    		hmapAllTrainings = allTrainingsFromDatabase();
	    		
	        	//After the operation of elimination we update the view
	        	setView();
	        	
	        	break;
	 
	        case R.id.send_training:
	        	
	        	sendFile(pos);    	
	        	
	        	break;
	    	default:
	    		Toast.makeText(this, getString(R.string.operation_failed),Toast.LENGTH_LONG).show();
	        	
	     }
	          
	     return true;          
      	
	}

	@Override
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method creates the options menu
	 */
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.training_history, menu);
		MenuItem searchItem = menu.findItem(R.id.action_search_bar);
		
		//Retrieval search view 
	    SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
	    
	    //we add the event of search view
		sv.setOnQueryTextListener(new OnQueryTextListener(){

			@Override
			public boolean onQueryTextChange(String text) {
				//Retrival of string that has been inserted by user and update the view
				stringSearch = text;
				setView();
				
				return true;
			}

			@Override
			public boolean onQueryTextSubmit(String text) {
				return true;
			}
			
		});
		
		return true;
	}
	
	@Override
	/**
	 * @author Danilo Dessi' & Meloni Pietro
	 * 
	 * This method associates the menu items with the actions 
	 */
	public boolean onOptionsItemSelected(MenuItem item) {
		
		switch(item.getItemId()) {
		
			case R.id.charge_training:
				showFileChooser(FILE_SELECT_CODE);
				break;
			case R.id.db_export:
				dba.exportDB();
				Toast.makeText(getApplicationContext(), getString(R.string.result_export), Toast.LENGTH_LONG).show();
				break;
			case R.id.db_import:
				//show fileChooser for choosing the db from the device.
				showFileChooser(DB_IMPORT_CODE);
		}
		
		return super.onOptionsItemSelected(item);
	}
	
	
	
	/******* METHODS TO MANAGE THE FUNCTIONS OF SEND AND LOAD OF THE FILES *******/
	
	/**
	 * @author Pietro Meloni
	 * @param code allow to use this function for more uses
	 * This method is meant for managing the selection of a 
	 * json file by file manager
	 */
	private void showFileChooser(int code) {
		
		//setting an intent ideal for a file chooser
	    Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 
	    
	    //this mean that all files are allowed
	    intent.setType("*/*"); 
	    intent.addCategory(Intent.CATEGORY_OPENABLE);

	    try {
	        startActivityForResult(
	                Intent.createChooser(intent, getString(R.string.file_chooser)), code);
	    } catch (android.content.ActivityNotFoundException ex) {
	        // Potentially direct the user to the Market with a Dialog
	        Toast.makeText(this, getString(R.string.no_file_chooser), 
	                Toast.LENGTH_SHORT).show();
	    }
	}
	
	/**
	 * @author Pietro Meloni
	 * @see FragmentActivity
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		
		//initialising variables
		File jsonFile = null;
		JSONObject jsonRetrieved = null;
		
		//request code can be many more than one. For now,
		//the request is just for loading a file
	    switch (requestCode) {
	    
	        case FILE_SELECT_CODE:
		        if (resultCode == RESULT_OK) {
		        	
		            // Get the Uri of the selected file 
		            Uri uriOfTheJsonFile = data.getData();
		      
		            
		            //if the last four charachters of the string aren't ".json", give an error notification
		            //to the user
		            if(uriOfTheJsonFile.toString().lastIndexOf(EXT, uriOfTheJsonFile.toString().length()-4) == -1) {
		            	Toast.makeText(this, getString(R.string.bad_json_extension), Toast.LENGTH_LONG).show();
		            	
		            } else {
		            
			            try {
			            	//retrieving the File from the file .json
							jsonFile = new File(new URI(uriOfTheJsonFile.toString()));
							
						} catch (URISyntaxException e) {
							
							e.printStackTrace();
							
						}
			            
			            try {
			            	
			            	//retrieving the JSONOBject from the File
							jsonRetrieved = JSONFileManager.readFromFile(jsonFile);
							
						} catch (IOException e) {
							
							e.printStackTrace();
						}
			            
			           //error managment
		            	if((jsonRetrieved == null)){
								
		            		Toast.makeText(this, getString(R.string.json_format_error), Toast.LENGTH_LONG ).show();
							super.onActivityResult(requestCode, resultCode, data);
							return;
							
						}
			            //Trying accessing name and date on jsonRetrieved
		            	//for error management purposes
						try {
							
							jsonRetrieved.getString(DATE);
							jsonRetrieved.getString(NAME);
							
						} catch (JSONException e1) {

							Toast.makeText(this, getString(R.string.json_format_error), Toast.LENGTH_LONG ).show();
							e1.printStackTrace();
						}
						
			            
			            //opening the database
			            dba.open();
			            
						//creating the row containing the training received
						dba.createTraining( jsonRetrieved);
			    		
			    		//closing the database
			    		dba.close();
			    		
			    		//Retrieval of all trainings from database
			    		hmapAllTrainings = allTrainingsFromDatabase();
			    		
			        	//After the operation, we update the view
			        	setView();
			            
			        }
		        }
	        break;
	        case DB_IMPORT_CODE:   
	        	
	        	if (resultCode == RESULT_OK) {
		        	
		            // Get the Uri of the selected file 
		            Uri uriOfTheImportedDB = data.getData();
		            Toast.makeText(getApplicationContext(), uriOfTheImportedDB.getPath(), Toast.LENGTH_LONG).show();
		           
		            //import the database
		            dba.importDB(uriOfTheImportedDB.getPath());
					
		            
		          //Retrieval of all trainings from database
		    		hmapAllTrainings = allTrainingsFromDatabase();
		    		
		        	//After the operation of elimination we update the view
		        	setView();
	        	}
	     
	    }
	    super.onActivityResult(requestCode, resultCode, data);
	}
	
	/**
	 * @author Danilo Dessi'
	 * This method is used to send a file
	 * @param pos The position in ListView of the training that must be sent
	 */
	private void sendFile(int pos) {
		
		//Intent to call the app to send file
		Intent intentSend = new Intent(Intent.ACTION_SEND);
		
		//We indicate the type of information that are sent
    	intentSend.setType("plain/text"); 
    	try {
    		
			//we create the json object from the json string
			JSONObject jo = new JSONObject(hmapAllTrainings.get(idKeys.get(pos)));
			
			//we insert the object of the email
			intentSend.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.training)+" : " + jo.getString(NAME) + " : " + jo.getString(DATE));
			
			//we insert a text of email
        	intentSend.putExtra(android.content.Intent.EXTRA_TEXT, getString(R.string.message_email));
        	
        	//we create a file on device
			JSONFileManager.saveOnFile(jo, jo.getString(NAME));
			
			//we insert the file as attachment
			Uri file = Uri.parse(PATH_FILE + "/" + jo.getString(NAME) + EXT);
        	intentSend.putExtra(Intent.EXTRA_STREAM, file);
        	
        	//If there is a app that manages the send action the new activity is launched
        	if (intentSend.resolveActivity(getPackageManager()) != null) {
        		startActivity(Intent.createChooser(intentSend, getString(R.string.title_send_file)));
        	} 
        	
        } catch (Exception e) {
        	
        	
			e.printStackTrace();
        }
    	
    }
	
	
	
	/******* METHODS TO MANAGE THE GRAPHIC *******/
	
	/**
	 * @author Danilo Dessi'
	 * The aim of this method is to set the view. It uses a thread to update a view because
	 * the update of the list view by filtered results is blocker
	 */
	private void setView() {		
		
		(new AsyncTask<Void, Void, Void> (){

			//Thread works a temporary array of keys and only at the end the array is assigned to idKeys
			ArrayList<String> tmpIdKeys;

			@Override
            protected void onPreExecute() {
                super.onPreExecute();   
                
            }
			
			@Override
			protected Void doInBackground(Void... arg0) {
				
				//start critical session
				try {
					semaphore.acquire();
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				
				//We adapt the hmapAllTraining in a adapter for the list
				tmpIdKeys = setAdapterForListView();
				
				//exit critical session
				semaphore.release();
				
				return null;
			}
						
			@Override
			protected void onPostExecute(Void value)  {
				super.onPostExecute(null);
				//we assign a new array of keys
				idKeys = tmpIdKeys;
				
				//If at least one is saved
				if( ! ( hmapAllTrainings.isEmpty() )) {
							
					//we set up the title of activity
					setTitle(getString(R.string.history_trainings));
					
					//we set up adapter on list view
					lvTrainings.setAdapter(adapter);
					
					//we set up the event on click on item of the list
					lvTrainings.setOnItemClickListener(new OnItemClickListener() {
			
						@Override
						public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
							/*
							 * When the user click on item of the list of trainings a new activity starts 
							 * to show details 
							 */
							Bundle b = new Bundle();
							b.putString("JSONObject", hmapAllTrainings.get(idKeys.get(pos)));
							intentTrainingDetails.putExtras(b);
							startActivity(intentTrainingDetails);
							
						}
					});
					
					//we connect the items of the list with a contextual menu
					registerForContextMenu(lvTrainings);
									
				} else {
					
					//if there aren't trainings we set up a empty view with this title
					setTitle(getString(R.string.no_trainings));
					lvTrainings.setAdapter(null);
					
				}
                
			}
			
		}).execute(null,null,null);
		
	}
		
	/**
	 * @author Danilo Dessi' & Andreas Scalas
	 * The aim of this method is to set the name and date of the training
	 * in a adapter to be show well in the list view. It manages the informations
	 * that the user inserts in searchView of ActionBar.
	 * The array list, that is returned by method, contains at position i the key of the item in position i in the adapter
	 * @return A array list with the keys of the element that are in the adapter for the list view
	 */
	private ArrayList<String> setAdapterForListView() {
		
		ArrayList<String> id = new ArrayList<String>();
		
		//The adapter for list
		ArrayList<Map<String,String>> arrayForAdapter = new ArrayList<Map<String,String>>();
		
		// A temporary Hashmap to set up the values in a adapter of type SimpleAdapter
		Map<String, String> tempHMapForSingleTraining;
		
		//The set of the keys of the trainings
		Set<String> keys = hmapAllTrainings.keySet();
		
		for(String key : keys){
			
			try {
				
				//we create the json object from the json string
				//jo = new JSONObject(hmapAllTrainings.get(key));
				
				//creates the new standardizer
				JSONStandardization standardJSON = new JSONStandardization();

				//convert the training json String of the hashmap in Training
				Training training = standardJSON.jsonObjectToTraining(hmapAllTrainings.get(key));
				
				if( compareStringFromsStartWithSearchString(training.getName()) ) {
					
					//add the key in the array id
					id.add(key);
					
					//we create a temporary HashMap
					tempHMapForSingleTraining = new HashMap<String, String>();
					
					//we insert into HashMap the name and the date of the training
					tempHMapForSingleTraining.put(NAME, training.getName());
					tempHMapForSingleTraining.put(DATE, training.getDate());
					tempHMapForSingleTraining.put(DISTANCE, format(training.totalDistance() * MULTIPLIER_FROM_METERS_TO_KILOMETERS) +" km");
					
					//we add in the adapter the temporary HashMap
					arrayForAdapter.add(tempHMapForSingleTraining);
					
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
		
		//To insert the value in the simple adapter is necessary to use two support array to map
		// the value (that are saved in HashMaps) in the correct text view of the layout of the list
		String from[] = {NAME, DATE, DISTANCE};
		int to[] = {R.id.name_training, R.id.date_training, R.id.distance_training};
		adapter = new SimpleAdapter(this, arrayForAdapter, R.layout.layout_list_history_trainings, from, to);
		
		return id;
	}
		
	
	
	/******* METHODS TO SUPPORT THE FUNCTIONS OF THE ACTIVITY *******/
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method delete a single training from database
	 * @param pos The position to retrieve the key of the training from database 
	 * @return true if the training is deleted, false otherwise
	 */
	private boolean deleteTraining(int pos){
		
		//A boolean to save the result of operation
		boolean result;
		
		//Retrieval the key of training
		String key = idKeys.get(pos);
		
		//Elimination of the training
		dba.open();
		result = dba.deleteTraining(Long.parseLong(key));
		dba.close();
		
		return result;
		
	}
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method retrieves all trainings from database
	 * @return a HashMap of pairs key-json_string_training
	 */
	private HashMap<String, String> allTrainingsFromDatabase(){
		
		dba = new DatabaseAdapter(getApplicationContext());
		
		dba.open();
		HashMap<String, String> hmap = dba.fetchAllTrainings();
		dba.close();
		
		return hmap;
	}
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method is used to match the name of the trainings with the stringSearch
	 * The matching operation start from first character of the strings
	 * @param str1 the string that represents the name of the training
	 * @return true if the name matchs with stringSearch, false otherwise
	 */
	private boolean compareStringFromsStartWithSearchString(String str1) {
		
		boolean result = true;
		
		if( ! (stringSearch.compareTo("") == 0) ) {
			
			//Retrieval length from strings
			int sizeStr1 = str1.length();
			int sizeStringSearch = stringSearch.length();
			
			//if length of stringSearch is greater of str1 then stringSearch is not contained in str1
			if(sizeStr1 < sizeStringSearch) {
				
				result = false;
				
			} else {
				
				//matching of sizeStringSearch characters
				String subStringStr1 = str1.substring(0, sizeStringSearch);
				result = subStringStr1.compareTo(stringSearch) == 0;
					
			}
		}
		
		return result;
		
	}
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This function is used to format the values that are shown in the view.
	 * @param a value that must be formatted
	 * @return a format string of the value
	 */
	public static String format (double value){
		
		DecimalFormat decimalFormat = new DecimalFormat("0.00");
		
		return decimalFormat.format( value );
		
	}
	
}
